---
import type { MarkdownHeading } from "astro";
import { siteConfig } from "../../config";
import { url } from "../../utils/url-utils";

interface Props {
	class?: string;
	headings: MarkdownHeading[];
}

let { headings = [] } = Astro.props;

let minDepth = 10;
for (const heading of headings) {
	minDepth = Math.min(minDepth, heading.depth);
}

const className = Astro.props.class;
// 修复路由判断逻辑，确保在文章页面能正确显示TOC
const isPostsRoute =
	Astro.url.pathname.includes("/posts/") || headings.length > 0;

const removeTailingHash = (text: string) => {
	let lastIndexOfHash = text.lastIndexOf("#");
	if (lastIndexOfHash !== text.length - 1) {
		return text;
	}

	return text.substring(0, lastIndexOfHash);
};

let heading1Count = 1;

const maxLevel = siteConfig.toc.depth;
---
<table-of-contents class:list={[className, "group"]} id="toc">
    <!-- TOC内容将由JavaScript动态生成 -->
</table-of-contents>

<script>
class TableOfContents extends HTMLElement {
    tocEl: HTMLElement | null = null;
    visibleClass = "visible";
    observer: IntersectionObserver;
    anchorNavTarget: HTMLElement | null = null;
    headingIdxMap = new Map<string, number>();
    headings: HTMLElement[] = [];
    sections: HTMLElement[] = [];
    tocEntries: HTMLAnchorElement[] = [];
    active: boolean[] = [];
    activeIndicator: HTMLElement | null = null;

    constructor() {
        super();
        this.observer = new IntersectionObserver(
            this.markVisibleSection, { threshold: 0 }
        );
    };

    markVisibleSection = (entries: IntersectionObserverEntry[]) => {
        entries.forEach((entry) => {
            const id = entry.target.children[0]?.getAttribute("id");
            const idx = id ? this.headingIdxMap.get(id) : undefined;
            if (idx != undefined)
                this.active[idx] = entry.isIntersecting;

            if (entry.isIntersecting && this.anchorNavTarget == entry.target.firstChild)
                this.anchorNavTarget = null;
        });

        if (!this.active.includes(true))
            this.fallback();
        this.update();
    };

    toggleActiveHeading = () => {
        let i = this.active.length - 1;
        let min = this.active.length - 1, max = -1;
        while (i >= 0 && !this.active[i]) {
            this.tocEntries[i].classList.remove(this.visibleClass);
            i--;
        }
        while (i >= 0 && this.active[i]) {
            this.tocEntries[i].classList.add(this.visibleClass);
            min = Math.min(min, i);
            max = Math.max(max, i);
            i--;
        }
        while (i >= 0) {
            this.tocEntries[i].classList.remove(this.visibleClass);
            i--;
        }
        if (min > max) {
            this.activeIndicator?.setAttribute("style", `opacity: 0`);
        } else {
            let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
            let scrollOffset = this.tocEl?.scrollTop || 0;
            let top = this.tocEntries[min].getBoundingClientRect().top - parentOffset + scrollOffset;
            let bottom = this.tocEntries[max].getBoundingClientRect().bottom - parentOffset + scrollOffset;
            this.activeIndicator?.setAttribute("style", `top: ${top}px; height: ${bottom - top}px`);
        }
    };

    scrollToActiveHeading = () => {
        // If the TOC widget can accommodate both the topmost
        // and bottommost items, scroll to the topmost item. 
        // Otherwise, scroll to the bottommost one.

        if (this.anchorNavTarget || !this.tocEl) return;
        const activeHeading =
            document.querySelectorAll<HTMLDivElement>(`#toc .${this.visibleClass}`);
        if (!activeHeading.length) return;

        const topmost = activeHeading[0];
        const bottommost = activeHeading[activeHeading.length - 1];
        const tocHeight = this.tocEl.clientHeight;

        let top;
        if (bottommost.getBoundingClientRect().bottom -
            topmost.getBoundingClientRect().top < 0.9 * tocHeight)
            top = topmost.offsetTop - 32;
        else
            top = bottommost.offsetTop - tocHeight * 0.8;

        this.tocEl.scrollTo({
            top,
            left: 0,
            behavior: "smooth",
        });
    };

    update = () => {
        requestAnimationFrame(() => {
            this.toggleActiveHeading();
            // requestAnimationFrame(() => {
            this.scrollToActiveHeading();
            // });
        });
    };

    fallback = () => {
        if (!this.sections.length) return;

        for (let i = 0; i < this.sections.length; i++) {
            let offsetTop = this.sections[i].getBoundingClientRect().top;
            let offsetBottom = this.sections[i].getBoundingClientRect().bottom;

            if (this.isInRange(offsetTop, 0, window.innerHeight)
                || this.isInRange(offsetBottom, 0, window.innerHeight)
                || (offsetTop < 0 && offsetBottom > window.innerHeight)) {                    
                this.markActiveHeading(i);
            }
            else if (offsetTop > window.innerHeight) break;
        }
    };

    markActiveHeading = (idx: number)=> {
        this.active[idx] = true;
    };

    handleAnchorClick = (event: Event) => {
        const anchor = event
            .composedPath()
            .find((element) => element instanceof HTMLAnchorElement);

        if (anchor) {
            event.preventDefault(); // 阻止默认的锚点跳转
            
            const id = decodeURIComponent(anchor.hash?.substring(1));
            const targetElement = document.getElementById(id);
            
            if (targetElement) {
                // 计算目标位置，与移动端保持一致
                const navbarHeight = 80; // 导航栏高度
                const targetTop = targetElement.getBoundingClientRect().top + window.pageYOffset - navbarHeight;
                
                // 使用与移动端相同的滚动方式
                window.scrollTo({
                    top: targetTop,
                    behavior: "smooth"
                });
            }
            
            const idx = this.headingIdxMap.get(id);
            if (idx !== undefined) {
                this.anchorNavTarget = this.headings[idx];
            } else {
                this.anchorNavTarget = null;
            }
        }
    };

    isInRange(value: number, min: number, max: number) {
        return min < value && value < max;
    };

    connectedCallback() {
        // wait for the onload animation to finish, which makes the `getBoundingClientRect` return correct values
        const element = document.querySelector('.custom-md') || document.querySelector('.prose') || document.querySelector('.markdown-content');
        if (element) {
            element.addEventListener('animationend', () => {
                this.init();
            }, { once: true });
        } else {
            // 如果没有找到动画元素，直接初始化
            console.debug('Animation element not found, initializing directly');
            setTimeout(() => this.init(), 100);
        }
    };

    init() {
        // 重新生成TOC内容
        this.regenerateTOC();

        this.tocEl = this;

        this.tocEl.addEventListener("click", this.handleAnchorClick, {
            capture: true,
        });

        this.activeIndicator = document.getElementById("active-indicator");

        this.tocEntries = Array.from(
            this.querySelectorAll<HTMLAnchorElement>("a[href^='#']")
        );

        if (this.tocEntries.length === 0) return;

        this.sections = new Array(this.tocEntries.length);
        this.headings = new Array(this.tocEntries.length);
        for (let i = 0; i < this.tocEntries.length; i++) {
            const id = decodeURIComponent(this.tocEntries[i].hash?.substring(1));
            const heading = document.getElementById(id);
            const section = heading?.parentElement;
            if (heading instanceof HTMLElement && section instanceof HTMLElement) {
                this.headings[i] = heading;
                this.sections[i] = section;
                this.headingIdxMap.set(id, i);
            }
        }
        this.active = new Array(this.tocEntries.length).fill(false);

        this.sections.forEach((section) =>
            this.observer.observe(section)
        );

        this.fallback();
        this.update();
    };

    regenerateTOC() {
        // 检查是否为文章页面
        const isPostPage = window.location.pathname.includes('/posts/') || 
                          document.querySelector('.custom-md, .markdown-content') !== null;
        
        if (!isPostPage) {
            // 如果不是文章页面，隐藏TOC
            this.innerHTML = '';
            return;
        }

        // 从当前页面重新获取标题
        const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'))
            .filter(h => h.id) // 只包含有id的标题
            .map(h => ({
                depth: parseInt(h.tagName.substring(1)),
                slug: h.id,
                text: h.textContent || ''
            }));
        
        // 如果没有标题，清空TOC
        if (headings.length === 0) {
            this.innerHTML = '';
            return;
        }

        // 重新生成TOC HTML
        const minDepth = Math.min(...headings.map(h => h.depth));
        const maxLevel = 3; // siteConfig.toc.depth
        let heading1Count = 1;
        
        const tocHTML = headings
            .filter(heading => heading.depth < minDepth + maxLevel)
            .map(heading => {
                const depthClass = heading.depth === minDepth ? '' : 
                    heading.depth === minDepth + 1 ? 'ml-4' : 'ml-8';
                const badgeContent = heading.depth === minDepth ? (heading1Count++) :
                    heading.depth === minDepth + 1 ? '<div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>' :
                    '<div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>';
                
                return `<a href="#${heading.slug}" class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2">
                    <div class="transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold ${depthClass} ${heading.depth === minDepth ? 'bg-[var(--toc-badge-bg)] text-[var(--btn-content)]' : ''}">
                        ${badgeContent}
                    </div>
                    <div class="transition text-sm ${heading.depth <= minDepth + 1 ? 'text-50' : 'text-30'}">${heading.text}</div>
                </a>`;
            }).join('');
        
        this.innerHTML = tocHTML + '<div id="active-indicator" style="opacity: 0" class="-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"></div>';
    }

    disconnectedCallback() {
        this.sections.forEach((section) =>
            this.observer.unobserve(section)
        );
        this.observer.disconnect();
        this.tocEl?.removeEventListener("click", this.handleAnchorClick);
    };
}

if (!customElements.get("table-of-contents")) {
    customElements.define("table-of-contents", TableOfContents);
}
</script>